import { runQueue } from '../utils/async'
import { START } from '../utils/route'

export default class History {
  constructor (router, base = '') {
    this.current = START // 当前路由
    this.router = router // 当前 路由实例
    this.pending = null // 当前目标跳转路由对象
    this.base = base // 基础路径
  }

  transitionTo (location, onComplete, onAbort) {
    const route = this.router.match(location)
    this.confirmTransition(route, () => {
      this.updateRoute(route) // 路由更新，随后视图渲染刷新
      onComplete && onComplete(route) // 调用onComplete回调函数，完成路由跳转
    })
  }

  listen (cb) {
    this.cb = cb
  }

  confirmTransition (route, onComplete, onAbort) {
    /*
    * queue：队列
    * vue-router在这里用array创建了一个队列
    * 意在将各钩子函数顺序化执行
    * */
    const queue = [].concat(
      this.router.beforeHooks
    )

    // 错误&路由取消调用函数
    // 一般是路由跳转一半，遇到 钩子函数 调用next(false)
    // 或者 调用next(新路由对象)，需要将当前路由跳转终止。
    // 如果调用abort函数，则队列里面剩下的钩子函数也不会执行
    const abort = err => {
      onAbort && onAbort(err)
    }

    this.pending = route
    const iterator = (hook, next) => {
      if (this.pending !== route) {
        // 如果实例里面pending的路由 !== route
        abort()
      }
      try {
        // 开始执行用户的before系列钩子函数。
        hook(route, this.current, (to) => {
          // 如果我们传入了false，终止路由跳转
          if (to === false) {
            abort()
          } else if (
            // 如果我们传入了字符串/对象，对象里面有path/name属性
            typeof to === 'string' ||
            (typeof to === 'object' && (typeof to.path === 'string' || typeof to.name === 'string'))
          ) {
            // 终止当前路由跳转
            abort()
            // 如果里面有replace函数
            if (typeof to === 'object' && to.replace) {
              this.replace(to)
            } else {
              // 那么走push函数
              this.push(to)
            }
          } else {
            // 执行下一个钩子函数
            next(to)
          }
        })
      } catch (e) {
        abort()
      }
    }

    // 执行队列
    runQueue(queue, iterator, () => {
      onComplete(route)
    })
  }

  updateRoute (route) {
    const prev = this.current
    this.current = route
    this.cb && this.cb(route)
    this.router.afterHooks.forEach(hook => {
      hook && hook(route, prev)
    })
  }
}
